
#ifndef __RSP_C_OBJECT__HEADER_FILE__
#define __RSP_C_OBJECT__HEADER_FILE__

#include "coid/comm/commtypes.h"
#include "coid/comm/binstream/circularstreambuf.h"
#include "rsp_def.h"
#include "c_update.h"


using namespace coid;


////////////////////////////////////////////////////////////////////////////////
///Reference state
struct rsp_c_object_state : public rsp_state
{
    typedef rsp_base* (*fnc_create_object)( rsp_base* ref );


    rsp_c_object_state()
    {
        _bnd = 0;
        _flags = 0;
        _ncmd = 0;
    }

    rsp_c_object_state( rsp_base* p, uint oid )
    {
        rsp_state::base = p;
        rsp_state::oid = oid;
        _bnd = 0;
        _flags = 0;
        _ncmd = 0;
    }

    ~rsp_c_object_state()
    {
        destroy_bound_object();
    }

    void destroy_bound_object()
    {
        if( _flags & fOWNED_BOUND )
            delete _bnd;
        _bnd = 0;
        _flags &= ~(fOWNED_BOUND|fNO_BOUND_OBJECT);
    }

    ///Read updates for frames
    bool read_diff( rsp_c_update& upd )
    {
        if( _scmdbuf.has_packets() )
            _scmdbuf.client_reset();

        uchar rep, nsto;
        *upd._bin >> rep >> nsto;
        //DASSERT( (uint)rep+nsto <= upd._maxcmd );
        uchar nst = nsto;

        RSPC_SETTINGS._wrk_cmdlengths.need_new(nst);
        upd._bin->xread_raw( RSPC_SETTINGS._wrk_cmdlengths.ptr(), RSPC_SETTINGS._wrk_cmdlengths.byte_size() );

        uint s = upd._nsrvskip;
        uint d = 0;
        uint i;
        for( i=0; i<s && nst>0; ++i ) {
            if( i >= rep ) {
                upd._bin->xread_raw_scrap( RSPC_SETTINGS._wrk_cmdlengths[d++] );
                --nst;
            }
        }

        if(!nst)
            return true;

        _scmdbuf.prepare_packet_to_write(i-s,false);
        for( ; nst>0; ++i )
        {
            if( i >= rep )
            {
                ushort len = RSPC_SETTINGS._wrk_cmdlengths[d++];

                if(len)
                {
                    _scmdbuf.write_packet( len, *upd._bin );

                    if( !upd._full && _lcmdbuf.is_set() )
                    {
                        if( _lcmdbuf->get_packet_length(i-s) != len )
                            ++upd._changed;
                    }
                    else
                        ++upd._changed;
                }

                --nst;
            }
            _scmdbuf.flush();
        }

        DASSERT( d == nsto );

        return true;
    }


    void create_local_buffer()
    {
        if( !_lcmdbuf.is_set() )
            _lcmdbuf = new rsp_commandbuffer;
    }

    void destroy_local_buffer()
    {
        _lcmdbuf.destroy();
    }

    void prepare_frame( uint frame )
    {
        if( _lcmdbuf.is_set() )
            _lcmdbuf->prepare_packet_to_read(frame);
    }

    binstream& push_cmd( uint frame )
    {
        if( _lcmdbuf->prepare_packet_to_write(frame,true) )
            ++_ncmd;
        return *_lcmdbuf;
    }

    ///Push client commands to stream
    /** @param dbr offset to advance from the ref state (and thus _curcmd)
    **/
    uint write_client_commands( binstream& bin, uint max, uint* cpos )
    {
        if( _ncmd == 0 ) {
            //no commands waiting
            bin << (uchar)0;
            return 0;
        }

        uint tsz;
        uint pos = _lcmdbuf->get_packet_lengths( 0, UMAX, RSPC_SETTINGS._wrk_cmdlengths, &tsz );
        uint nst = RSPC_SETTINGS._wrk_cmdlengths.size() - pos;

        DASSERT(nst);
        DASSERT( pos+nst <= max );

        //inc.update starts with number of transferred states followed by
        // the number of empty frames from the last acked state
        bin << (uchar)nst;
        bin << (uchar)pos;

        //write command lengths
        bin.xwrite_raw( RSPC_SETTINGS._wrk_cmdlengths.ptr()+pos, nst * sizeof(ushort) );

        circularstreambuf::pckdata a,b;
        _lcmdbuf->get_packet_data( pos, a, b, nst );
        bin.xwrite_raw( a.ptr, a.size );
        if( b.size )  bin.xwrite_raw( b.ptr, b.size );

        //DASSERT( cdcd_memcheck( a.ptr, a.ptr+a.size, b.ptr, b.ptr+b.size ) );
        DASSERT( a.size + b.size == tsz );

        *cpos = pos;
        return _ncmd;
    }


    ///Synchronize the bound state with the reference state
    void sync_bound()
    {
        DASSERT( rsp_state::base );

        if( _flags & fNO_BOUND_OBJECT )
            return;

        if(!_bnd) {
            create_bound_object();
            _flags |= fOWNED_BOUND;
            sync_bound();
        }
        else {
            _bnd->copy_construct(*rsp_state::base);
            //_bnd->set_oid(UMAX);
        }
        //*_bnd = *_ref;
    }

    rsp_base* create_bound_object()
    {
        fnc_create_object fn = find_class( class_name() );
        if( !fn || 0 == (_bnd = fn(rsp_state::base)) )
            _flags |= fNO_BOUND_OBJECT;

        return _bnd;
    }

    void set_object( rsp_base* p, uint oid )
    {
        if( p != rsp_state::base ) {
            p->state = this;
            rsp_state::base = p;
            rsp_state::oid = oid;
        }
    }

    rsp_base* set_bound_object( rsp_base* p, bool ownit )
    {
        if( !p && !_bnd )
            p = create_bound_object();
        else if(!p)
            p = _bnd;
        else if(_bnd)
        {
            if( _bnd != p )
            {
                destroy_bound_object();
                _bnd = p;
            }
        }
        else
            _bnd = p;

        if(ownit)
            _flags |= fOWNED_BOUND;
        
        return _bnd;
    }

    uint oid() const                        { return rsp_state::oid; }
    rsp_base* object_ptr() const            { return rsp_state::base; }

    rsp_base* bound_object_ptr() const      { return _bnd ? _bnd : rsp_state::base; }
    rsp_base* ref_object_ptr() const        { return rsp_state::base; }

    static rsp_c_object_state* get_from_ptr( rsp_base* p )
    {   return static_cast<rsp_c_object_state*>( p->state ); }

    static rsp_c_object_state* get_from_oid( uint oid );

    token class_name() const                { return rsp_state::base->class_name(); }

    ///Do physics step of the ref object one frame forward
    uint frame_ref()
    {
        uint bsr=0;
        //T should have a 'void frame()' and 'void command( uchar cmd, binstream& )' methods defined

        uints pck0 = _scmdbuf.anything_to_read_pck0();
        if( pck0 )
        {
            if( _lcmdbuf.is_set()  &&  pck0 != _lcmdbuf->anything_to_read_pck0() )
                bsr=1;

            while( _scmdbuf.bytes_to_read() )
            {
                uchar cd;
                _scmdbuf >> cd;
                //DASSERT( bsr==0 || cd == 0xff );
                if( cd == 0xff )  break;        //leave full update at the end
                rsp_state::base->command( cd, _scmdbuf );
            }
        }
        else if( _lcmdbuf.is_set()  &&  pck0 != _lcmdbuf->anything_to_read_pck0() )
            bsr=1;

        if( rsp_state::base )
            rsp_state::base->frame();

        return bsr;
    }

    uint frame_ref_final()
    {
        uint bsr=0;
        //if there's something left in the buffer, it would be a full update
        if( _scmdbuf.bytes_to_read() ) {
            rsp_state::base->stream_in(_scmdbuf);
            bsr = 1;
        }

        if( _scmdbuf.has_packets() )
            _scmdbuf.acknowledge();
        return bsr;
    }

    void frame_bnd( uint frame )
    {
        if(!_bnd) return;

        rsp_commandbuffer* cmd=0;
        if( _lcmdbuf.is_set() )
        {
            if( _lcmdbuf->anything_to_read(frame) )
                cmd = _lcmdbuf.ptr();
        }
        else if( _scmdbuf.anything_to_read(frame) )
            cmd = &_scmdbuf;

        if(cmd)
        {
            //T should have a 'void frame()' and 'void command( uchar cmd, binstream& )' methods defined
            while( cmd->bytes_to_read() > 0 )
            {
                uchar cd;
                *cmd >> cd;
                DASSERT( cd != 0xff );      //no full update can be available for speculative states
                _bnd->command( cd, *cmd );
            }
        }

        _bnd->frame();
    }

    void eat_local_command()
    {
        if( _lcmdbuf->bytes_to_read_pck0() ) {
            DASSERT( _ncmd>0 );
            --_ncmd;
        }
        if( _lcmdbuf->has_packets() )
            _lcmdbuf->acknowledge(true);
        //_lcmdbuf->flush();
    }

    void eat_local_commands( uint n )
    {
        uint na = _lcmdbuf->packets();
        if( n > na )
            n = na;

        for( ; n>0; --n ) {
            if( _lcmdbuf->bytes_to_read_pck0() )
                --_ncmd;
            _lcmdbuf->acknowledge(true);
        }
    }

    uint packets() const                { return _scmdbuf.packets(); }



    static int register_class( const token& name, fnc_create_object fn )
    {
        get_classreg().insert_key_value( name, fn );
        return 0;
    }

    static fnc_create_object find_class( const token& name )
    {
        fnc_create_object* p = get_classreg().find_value(name);
        return p ? *p : 0;
    }

protected:

    //local<rsp_base> _ref;                   ///< managed reference object
    rsp_base* _bnd;                         ///< interpolated object

protected:

    typedef hash_map<charstr,fnc_create_object> t_hash;
    static t_hash& get_classreg()
    {
        static t_hash hash;
        return hash;
    }

    ushort _ncmd;
    ushort _flags;
    enum {
        fOWNED_BOUND            = 1,
        fNO_BOUND_OBJECT        = 2,
    };

    local< rsp_commandbuffer > _lcmdbuf;    ///< local (owned) command buffer
    rsp_commandbuffer _scmdbuf;             ///< server sent command buffer
    //rsp_commandbuffer _ccmdbuf;             ///< local command buffer
/*
    ///Command buffer holding the server-sent commands from waiting queue.
    ///It's a circular buffer updated often. (size:nw)
    dynarray<t_cmdbuffer> _wcmdbuf;

    ///Command buffer holding the server-sent commands from trailing queue.
    ///It's written backwards.
    t_cmdbuffer _qcmdbuf;

    ///Local command buffer, keeps locally issued client commands
    ///This buffer is used to send commands to server. It's meaningful
    /// to send just the commands that can be accepted by the server,
    /// that is (nw) commands before current frame. (size:nw+1)
    dynarray<t_cmdbuffer> _loccmdbuf;
*/

protected:
    //static void glob_register_class( const token& name, rsp_c_object_state::fnc_create_object cid, void* context, bool noref );

};


#define RSPC_CLASS(n,c) \
    static rsp_base* fn_creator__##c( rsp_base* ref )  { return new c; } \
    static int auto_register__##c = rsp_c_object_state::register_class( #n, &fn_creator__##c )

#define RSPC_CLASS_REG(c,creator) \
    static int auto_register__##c = rsp_c_object_state::register_class( #c, &creator )


#endif //__RSP_C_OBJECT__HEADER_FILE__
